//
//  URLConnectionViewController.m
//  网络
//
//  Created by hfzhangzhang on 2018/4/11.
//  Copyright © 2018年 hfzhangzhang. All rights reserved.
//
/*
 NSURLConnectionDownloadDelegate 千万不要用!!! 专门针对杂志的下载提供的接口
 如果在开发中使用DownloadDelegate 下载.能够监听下载进度,但是无法拿到下载文件!
 Newsstand Kit’s 专门用来做杂志!!!
 */
#import "URLConnectionViewController.h"

@interface URLConnectionViewController ()<NSURLConnectionDataDelegate>
/** 要下载文件的总大小 */
@property(assign,nonatomic)long long  expectedContentLength;
/** 当前下载的长度 */
@property(assign,nonatomic)long long currentLength;

/** 保存目标 */
@property(copy,nonatomic)NSString * tartgetFilePath;
/** 用来每次接受到的数据,拼接  */
@property(nonatomic,strong)NSMutableData * fileData;



/** 保存文件的输出流
 - (void)open;   写入之前,打开流
 - (void)close;  完成之后,关闭流
 
 */

@property(nonatomic,strong)NSOutputStream * fileStream;


/** 下载线程的运行循环 */
@property(assign,nonatomic)CFRunLoopRef downloadRunloop;


@end

@implementation URLConnectionViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
}



/*
 问题:
 1.没有下载进度 ,会影响用户体验
 2.内存偏高,有一个最大峰值:
 
 
 解决办法:
 - 通过代理方式来解决!!
 1.进度跟进!
 - 在响应方法中获得文件总大小!
 - 每次接收到数据,计算数据的总比例!
 2.保存文件的思路?
 - 保存完成写入磁盘
 测试结果:和异步方法执行的效果一样.仍然存在内存问题!
 推测:苹果的异步方法的实现思路.就是刚才我们的实现思路!!
 
 - 边下载,边写
 1.NSFileHandle 测地解决了内存峰值的问题!
 2.NSOutputStream 输出流
 
 新的问题:
 默认Connection 是在主线程工作,指定了代理的工作队列之后,整个下载仍然是在主线程!!UI事件能够卡住下载!
 
 */

-(void)connection
{
    
    // 将网络操作放在异步线程,异步的运行循环不启动,没有办法监听到网络事件!!
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        //1.url
        NSString * urlStr = @"http://127.0.0.1/002--加密算法介绍.wmv";
        urlStr = [urlStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
        NSURL * url = [NSURL URLWithString:urlStr];
        
        //2.request
        NSURLRequest * request = [NSURLRequest requestWithURL:url];
        
        //3.connection
        NSLog(@"开始 %@",[NSThread currentThread]);
        /*
         For the connection to work correctly, the calling thread’s run loop must be operating in the default run loop mode.
         为了保证连接的正常工作,调用线程的RunLoop 必须运行在默认的运行循环模式下!!
         */
        NSURLConnection * conn = [NSURLConnection connectionWithRequest:request delegate:self];
        //设置代理工作的操作队列
        [conn setDelegateQueue:[[NSOperationQueue alloc]init]];
        //4.启动连接
        [conn start];
        
        //5. 启动运行循环
        //CoreFoundation 框架 CFRunloop
        /*
         CFRunLoopStop(r)        停止指定的RunLoop
         CFRunLoopGetCurrent()   拿到当前的RunLoop
         CFRunLoopRun();         直接启动当前的运行循环
         */
        //1.拿到当前线程的运行循环
        self.downloadRunloop = CFRunLoopGetCurrent();
        //2.启动运行循环
        CFRunLoopRun();
        NSLog(@"来了!!");
    });
    
    
    
    
    //1.url
    NSString * urlStr = @"http://127.0.0.1/002--加密算法介绍.wmv";
    urlStr = [urlStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
    
    NSURL * url = [NSURL URLWithString:urlStr];
    
    
    //2.request
    NSURLRequest * request = [NSURLRequest requestWithURL:url];
    
    //3.connection
    NSLog(@"开始");
    NSURLConnection * conn = [NSURLConnection connectionWithRequest:request delegate:self];
    //启动连接
    [conn start];
}


#pragma mark - <NSURLConnectionDataDelegate>


#pragma mark - <NSURLConnectionDataDelegate>


//1.接受到服务器的响应  - 状态行&响应头 - 做一些准备工作
//expectedContentLength  需要下载文件的总大小 long long
//suggestedFilename      服务器建议保存的文件名称

-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
    NSLog(@"%@",response);
    //记录文件总大小
    self.expectedContentLength = response.expectedContentLength;
    self.currentLength = 0;
    //生成目标文件路径
    self.tartgetFilePath = [@"/Users/h/Desktop/123" stringByAppendingPathComponent:response.suggestedFilename];
    //删除 removeItemAtPath 如果文件存在,就会直接删除,如果文件不存在,就什么也不做!!也不会报错!
    [[NSFileManager defaultManager] removeItemAtPath:self.tartgetFilePath error:NULL];
    
    //输出流  以追加的方式打开文件流
    // self.fileStream = [[NSOutputStream alloc]initToFileAtPath:self.tartgetFilePath append:YES];
    // [self.fileStream open];
    
}




-(NSMutableData *)fileData
{
    if (!_fileData) {
        _fileData = [[NSMutableData alloc]init];
    }
    return _fileData;
}

//2.接受到服务器的数据 - 此代理方法可能会执行很多次!! 因为拿到多个data
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
    self.currentLength += data.length;
    //计算百分比
    // progress = long long / long long
    float progress = (float)self.currentLength / self.expectedContentLength;
    
    NSLog(@"%f",progress);
    //拼接数据
    [self.fileData appendData:data];
    
    //拼接数据
   // [self writeToFileWithData:data];
    

    
    //将数据追加到文件流中
   // [self.fileStream write:data.bytes maxLength:data.length];
    
}


//
-(void)writeToFileWithData:(NSData *)data{
    /*
     NSFileManager: 主要功能,创建目录,检查目录是否存在,遍历目录,删除文件.. 针对文件操作!! Finder
     NSFileHandle: 文件"句柄"(文件指针),Handle 意味着是对前面的单词的"File"操作
     主要功能,就是对同一个文件进行二进制的读和写!
     */
    //注意: 如果文件不存在,fp 在实例化的结果是空
    NSFileHandle  * fp = [NSFileHandle fileHandleForWritingAtPath:self.tartgetFilePath];
    //判断文件是否存在 - 如果文件不存在,直接将数据写入磁盘!
    if (fp == nil) {
        [data writeToFile:self.tartgetFilePath atomically:YES];
    }else{
        //1.将文件指针移动到文件的末尾!!
        [fp seekToEndOfFile];
        //2.写入文件
        [fp writeData:data];
        //3.关闭文件,在c语言的开发中,凡是涉及到文件读.写 打开和关闭的操作!!
        [fp closeFile];
    }
}

//3.所有的数据加载完毕 - 所有数据都传输完毕,只是一个最后的通知
-(void)connectionDidFinishLoading:(NSURLConnection *)connection
{
    NSLog(@"完毕");
    //将数据写入磁盘
    [self.fileData writeToFile:self.tartgetFilePath atomically:YES];
    //释放filedata
    self.fileData = nil;
    
    //关闭文件流
   // [self.fileStream close];
    
    //停止下载线程所在的运行循环
    CFRunLoopStop(self.downloadRunloop);
}

//4.下载失败或者错误
-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
    
}



/*
 问题:
 1.没有下载进度 ,会影响用户体验
 2.内存偏高,有一个最大峰值:
 */

- (void)sendAsynchronous{
    //1.url
    NSString * urlStr = @"http://127.0.0.1/002--加密算法介绍.wmv";
    urlStr = [urlStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
    NSURL * url = [NSURL URLWithString:urlStr];
    //2.request
    NSURLRequest * request = [NSURLRequest requestWithURL:url];
    //3.connection
    NSLog(@"开始");
    /*
     NSURLConnection - 从 iOS 2.0 开始
     异步加载  iOS 5.0 才有的, 在 5.0 以前,是通过代理来实现网络开发!!
     
     - 开发简单的网络网络请求还是比较方便的,直接用异步方法!!
     - 开发复杂的网络请求,步骤非常繁琐!!
     
     */
    [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse * _Nullable response, NSData * _Nullable data, NSError * _Nullable connectionError) {
        //将数据写入磁盘  data首先是在内存里面!!然后一次性写入到磁盘!
        [data writeToFile:@"/Users/h/Desktop/123/123.wmv" atomically:YES];
        
        NSLog(@"完成");
    }];
}

/** 指针的指针
在 C/OC/C++ 中,指针的指针通常使用来在一个方法中返回多个数值!!

今后看到参数是 **  哥么就是一个指向对象的指针!!!

nil     是地址指向NULL的空对象,在OC/C++中,给nil对象发送消息,相当于"放屁"
NULL    是空地址,本身就是0,就是一个整数,不能给NULL发送消息

什么是安全释放!!
[objc release]; 释放之后,不会修改对象的指针地址.
后续如果继续给objc 发送消息,就会出现野指针错误!!
objc = nil;//对象地址已经指向了 NULL(0),这个时候再发消息,都不会报错!!

*/



-(void)demo1{
    /**
     同步方法的应用场景
     1.抓取网络数据,如果开启多条线程异步抓取,很容易被封IP
     2.加载本地文件,可以直接使用同步方法,比较简单
     3. 加载要下载文件的头部信息,HEAD方法
     */
    
    NSURL * url = [NSURL URLWithString:@"http://127.0.0.1/abc.json"];
    NSURLRequest * request = [NSURLRequest requestWithURL:url];
    
    /**
     同步方法是一个阻塞的!
     参数:
     1.reques 请求
     2.Response    *__autoreleasing * 是服务器返回的响应的地址!
     3.错误的地址!
     */
    
    NSURLResponse * response = nil;
    NSData * data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:NULL];
    //反序列化
    id result = [NSJSONSerialization JSONObjectWithData:data options:0 error:NULL];
    NSLog(@"%@ %@",result,response);
    
    
}


@end
